"""聚类算法属于无监督学习，其中最常见的是均值聚类，scikit-learn中，有两种常用的均值聚类算法：
一种是有名的K-means（也就是K-均值）聚类算法，这个算法几乎是学习聚类必会提到的算法；
另一个是均值偏移聚类，它与K-means各有千秋，只是针对的应用场景不太一样，但是知名度远不如K-Means。

本篇介绍如何在scikit-learn中使用这两种算法。

1. 算法概述
1.1. K-Means
K-means算法起源于1967年，由James MacQueen和J. B. Hangar提出。
它的基本原理是是将n个点划分为K个集群，使得每个点都属于离其最近的均值（中心点）对应的集群。

K-Means算法主要包含2个部分：

距离公式：通常采用欧几里得距离来计算数据点与质心之间的距离
d(Xi,Cj)=||Xi−Cj||2 其中，Xi是数据点，Cj是质心。目标函数：
目标是最小化所有数据点与所属簇的质心之间的距离平方和J=∑kj=1∑Nji=1||Xi−Cj||2 其中，Nj表示第j个簇中的样本数量。

1.2. 均值漂移
均值漂移算法最早是由Fukunaga等人在1975年提出的。
它的基本原理是对每一个数据点，算法都会估算其周围点的密度梯度，然后沿着密度上升的方向移动该点，直至达到密度峰值。

均值漂移算法主要有3个步骤：

用核函数估计数据点的密度：常用的核函数比如高斯核，
K(x)=exp(−||x||2/(2h2)) 其中，h为带宽参数，控制核的宽度。

均值漂移向量：也就是对于每个数据点，计算其周围点的密度梯度
迭代更新：根据均值漂移向量，每个数据点会沿着密度上升的方向移动，更新自己的位置
2. 创建样本数据
利用scikit-learn中的样本生成器，创建一些用于聚类的数据。
"""
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from sklearn.cluster import MeanShift
import matplot_config

matplot_config.init_config()
X, y = make_blobs(n_samples=1000, centers=5)
plt.scatter(X[:, 0], X[:, 1], marker="o", c=y, s=25)

plt.show()
# image.png
# 生成了包含5个类别的1000条样本数据。
#
# 3. 模型训练
# 首先，划分训练集和测试集。


# 分割训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
"""按照8:2的比例划分了训练集和测试集。

3.1. K-Means
对于K-Means算法来说，需要指定聚类的数目，通过观察数据，我们指定聚类的数目5。
这里的样本数据比较简单，能够一下看出来，实际情况下并不会如此容易的知道道聚类的数目是多少，
常常需要多次的尝试，才能得到一个比较好的聚类数目，也就是K的值。

基于上面的数据，我们设置5个簇，看看聚类之后的质心在训练集和测试集上的表现。"""


# 定义
reg = KMeans(n_clusters=5, n_init="auto")

# 训练模型
reg.fit(X_train, y_train)

# 绘制质心
_, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
markers = ["x", "o", "^", "s", "*"]
centers = reg.cluster_centers_

axes[0].scatter(X_train[:, 0], X_train[:, 1], marker="o", c=y_train, s=25)
axes[0].set_title("【训练集】的质心位置")

axes[1].scatter(X_test[:, 0], X_test[:, 1], marker="o", c=y_test, s=25)
axes[1].set_title("【测试集】的质心位置")

for idx, c in enumerate(centers):
    axes[0].plot(c[0], c[1], markers[idx], markersize=10)
    axes[1].plot(c[0], c[1], markers[idx], markersize=10)

plt.show()
"""image.png

3.2. 均值漂移
均值漂移聚类，事先是不用指定聚类的数目的，通过调整它的bandwidth参数，
可以训练出拥有不同数目质心的模型。

下面，设置了bandwidth=5，训练之后得到了拥有3个质心的模型。"""


# 定义
reg = MeanShift(cluster_all=False, bandwidth=5)

# 训练模型
reg.fit(X, y)

# 绘制质心
_, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
markers = ["x", "o", "^", "s", "*"]
centers = reg.cluster_centers_
print(len(centers))

axes[0].scatter(X_train[:, 0], X_train[:, 1], marker="o", c=y_train, s=25)
axes[0].set_title("【训练集】的质心位置")

axes[1].scatter(X_test[:, 0], X_test[:, 1], marker="o", c=y_test, s=25)
axes[1].set_title("【测试集】的质心位置")

for idx, c in enumerate(centers):
    axes[0].plot(c[0], c[1], markers[idx], markersize=10)
    axes[1].plot(c[0], c[1], markers[idx], markersize=10)

plt.show()
"""image.png
它把左下角的3类比较接近的样本数据点算作一类。
通过调整 bandwidth参数，也可以得到和 K-Means 一样的结果，
有兴趣的话可以试试，大概设置 bandwidth=2 左右的时候，可以得到5个质心，与上面的K-Means算法的结果类似。

4. 总结
K-Means和均值漂移聚类都是强大的聚类工具，各有其优缺点。

K-Means 的优势是简单、快速且易于实现，当数据集是密集的，且类别之间有明显的分离时，效果非常好；
不过，它需要预先设定簇的数量k，且对初始质心的选择敏感，所以，对于不是凸形状或者大小差别很大的簇，效果并不好。

而均值漂移聚类的优势在于不需要预先知道簇的数量，可以自适应地找到数据的“模式”，对噪声和异常值也有很好的鲁棒性。
不过，与K-Means相比，它需要选择合适的带宽参数，对高维数据可能不太有效，且计算复杂度较高。

最后，对于这两种均值聚类算法来说，选择哪种取决于数据的性质和应用的需求。"""